Conversation
|
@zth Can you test with it? |
|
Looks good! Let me try it in a bit. We also need the external mentioned in the issue (#6398) and autowrap the function with that so what we get back is a valid React component, for this to be usable. |
Ah! You are correct. I missed that part. |
|
Tested and it compiles as expected 👍 |
|
There are two ways to do this
In a project using Server Component, do you see many patterns being used with client components of the React.componet type? If so, I think number 2 might be a little tricky. |
|
For example, it is not compiled let f = a => Js.Promise.resolve(a + a)
module C = {
@react.component
let make = async (~a) => {
let a = await f(a)
<div> {React.int(a)} </div>
}
}
let _ =
<div>
<C /> // Is this a common usage pattern?
</div> |
|
I think way 1 is the best approach. React has stated that client components won't support async/await because of technical limitations, but they've changed things like this before. Conceptually, I'd say it's still a @cristianoc any thoughts? |
If I understand correctly. |
Yeah exactly. So it's the same situation as React users have in JS/TS. |
|
@mununki works great! Completion does not work for the component, but that's something for the editor tooling. |
|
I encounter the same issue to #6304 (comment) |
|
Is that one mostly about polyvariants/objects without type annotations? Or are there other cases too where it triggers/fails? |
Polymorphic type seems making failed. |
|
Any updates guys? |
|
Me and @cristianoc had a chat about this and identity functions won't work for wrapping the full function. But, here's an approach that does seem to work: external reactPromise: promise<React.element> => React.element = "%identity"
module AsyncComponent = {
type props<'status> = {status: 'status}
let make = async ({status}) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
let make = props => {
make(props)->reactPromise
}
}
let jsx = <AsyncComponent status=#on />@mununki what do you think? |
|
It seems working! let f = a => Js.Promise.resolve(a + a)
module C = {
@react.component
let make = async (~a) => {
let a = await f(a)
<div> {React.int(a)} </div>
}
}
let _ =
<div>
<C a=1 />
</div>
module C1 = {
@react.component
let make = async (~status) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
}
let jsx = <C1 status=#on /> |
|
Fantastic! 😍 What do you think about this approach, good to you? |
zth
left a comment
There was a problem hiding this comment.
All seems to be working, right? @cristianoc could have a look as well perhaps
Thanks to your hint, I realized that the PRs should be approached differently - the two PRs had similar approaches, but different problems to solve. Thank you. |
|
The other PR I mentioned is #6304 |
Js output looks fine to me. Curious about how it runs in the runtime of RSC. |
|
@mununki it seems to work well, although I'm slightly confused by the JS output. Here's my example in code ( type user = {
id: string,
name: string,
}
let getUser = async id => {
{id, name: id}
}
@react.component
let make = async (~id) => {
let user = await getUser(id)
<div> {React.string(user.name)} </div>
}And here's the emitted JS: // Generated by ReScript, PLEASE EDIT WITH CARE
import * as JsxRuntime from "react/jsx-runtime";
async function getUser(id) {
return {
id: id,
name: id
};
}
async function make(param) {
var user = await getUser(param.id);
return JsxRuntime.jsx("div", {
children: user.name
});
}
var ServerComponent = make;
var make$1 = ServerComponent;
export {
getUser ,
make$1 as make,
}
/* react/jsx-runtime Not a pure module */This is great and works as intended, but I don't see the intermediate function that casts to React.element emitted anywhere. Is that perhaps optimized away? That's great if so. EDIT: To be clear, I've tested the above in a Next JS app using server components. And it works as expected. |
When I looked at the rawlambda, it appears to be optimized and gone, as you said. module React = {
let string = s => s
}
type user = {
id: string,
name: string,
}
let getUser = async id => {
{id, name: id}
}
@react.component
let make = async (~id) => {
let user = await getUser(id)
{React.string(user.name)}
} |
|
That's perfect, better than I expected. Good technique to keep in mind for future use cases. One last look from @cristianoc and then we should be good to go, right. |
| in | ||
| {binding with pvb_expr = removeArityRecord binding.pvb_expr} | ||
|
|
||
| let add_async_attribute ~async (body : Parsetree.expression) = |
There was a problem hiding this comment.
Aren't these functions already in the file that deals with async transformations?
There was a problem hiding this comment.
If you mean the files that deal with async transformation are ast_async.ml or ast_attributes.ml, the syntax module doesn't have the frontend as dependency yet. That's why I added here again.
Do you want me to add the frontend module to deps?
There was a problem hiding this comment.
Oops, not able to do that due do circular deps: frontend -> common -> syntax
There was a problem hiding this comment.
Looks like front-end already accesses ml:
(library
(name frontend)
(wrapped false)
(flags
(:standard -w +a-4-9-40-42-70))
(libraries common ml))and ml is where e.g. ast_untagged_variants.ml is.
There was a problem hiding this comment.
Maybe I'm missing something. syntax can access ml, but not frontend that has async transformation functions.
There was a problem hiding this comment.
Perhaps my wording is confusing. Actually, this is correct: frontend <- common <- syntax
There was a problem hiding this comment.
Put it where ast_untagged_variants.ml is, and change nothing else.
There was a problem hiding this comment.
It's not allowed to use anything that is not already in ml, obviously.
cristianoc
left a comment
There was a problem hiding this comment.
Looks great.
Left some comments about potential code duplication.
| in | ||
| {binding with pvb_expr = removeArityRecord binding.pvb_expr} | ||
|
|
||
| let is_async : Parsetree.attribute -> bool = |
There was a problem hiding this comment.
would you move this function too?
There was a problem hiding this comment.
How does it look? 9d283ba
I moved is_async and is_await to ast_async and ast_await instead of moving ast_attributes.ml to ml. If we are going to move ast_attributes.ml, then other modules need to be moved too such as external_arg_spec.ml, bs_syntaxerr.ml. I think that is a bit too much.
There was a problem hiding this comment.
Looks great.
Good to merge
| let is_async : Parsetree.attribute -> bool = | ||
| fun ({txt}, _) -> txt = "async" || txt = "res.async" | ||
|
|
||
| let async_component ~async expr = |
There was a problem hiding this comment.
this should not move as it's specific to the ppx
Resolve #6398
I've made it so that where the user defines async is considered an attribute of pvb_expr = Pexp_fun of component function. If you want the user to use it elsewhere, this will require additional implementation, let me know if you have any ideas.